package cn.mapway.tools.db;

import cn.mapway.plugin.db.DataTypeMapper;
import cn.mapway.tools.db.naming.CamelConvert;
import cn.mapway.tools.db.naming.INameConvertor;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.squareup.javapoet.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.entity.annotation.*;
import org.nutz.json.Json;
import org.nutz.lang.Lang;
import org.nutz.lang.Strings;
import org.springframework.stereotype.Component;
import schemacrawler.inclusionrule.RegularExpressionInclusionRule;
import schemacrawler.schema.Column;
import schemacrawler.schema.Table;
import schemacrawler.schema.View;
import schemacrawler.schema.*;
import schemacrawler.schemacrawler.*;
import schemacrawler.schemacrawler.exceptions.SchemaCrawlerException;
import schemacrawler.tools.utility.SchemaCrawlerUtility;
import us.fatehi.utility.datasource.DatabaseConnectionSource;
import us.fatehi.utility.datasource.DatabaseConnectionSources;
import us.fatehi.utility.datasource.MultiUseUserCredentials;

import javax.lang.model.element.Modifier;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.Timestamp;
import java.util.*;

/**
 * NutzImpl
 *
 * @author zhangjianshe@gmail.com
 */
@Slf4j
public class NutzImpl {
    IConfigure configure;
    INameConvertor camelConvert = new CamelConvert();

    public NutzImpl(IConfigure configure) {
        this.configure = configure;
    }

    private DatabaseConnectionSource getConnection() {
        final String connectionUrl = configure.getJdbcUrl();
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        System.out.println(Json.toJson(drivers));
        final DatabaseConnectionSource dataSource = DatabaseConnectionSources.newDatabaseConnectionSource(
                connectionUrl, new MultiUseUserCredentials(configure.getUser(), configure.getPassword()));
        return dataSource;
    }

    public void run() {
        log.info("Nutz export");
        if (Strings.isBlank(configure.getSchema())) {
            log.warn("没有定义数据库schema,本次操作不会生成任何DAO代码");
            return;
        }

        final List<String> includes = configure.includes();
        final List<String> excludes = configure.excludes();

        // Create the options
        final LimitOptionsBuilder limitOptionsBuilder =
                LimitOptionsBuilder.builder()
                        .includeSchemas(new RegularExpressionInclusionRule(configure.getSchema()))
                        .includeAllSequences()
                        .includeTables(tableFullName -> {
                            if (tableFullName.contains("$"))
                                return false;
                            int index = tableFullName.lastIndexOf('.');

                            String tableName = tableFullName;
                            if (index >= 0) {
                                tableName = tableFullName.substring(index + 1);
                            }
                            if (includes.size() > 0) {
                                return inList(includes, tableName);
                            } else {
                                return true;
                            }
                        });

        final LoadOptionsBuilder loadOptionsBuilder =
                LoadOptionsBuilder.builder()
                        // Set what details are required in the schema - this affects the
                        // time taken to crawl the schema
                        .withInfoLevel(InfoLevel.maximum)
                        .withSchemaInfoLevel(SchemaInfoLevelBuilder.maximum());

        final SchemaCrawlerOptions options =
                SchemaCrawlerOptionsBuilder.newSchemaCrawlerOptions()
                        .withLimitOptions(limitOptionsBuilder.toOptions())
                        .withLoadOptions(loadOptionsBuilder.toOptions());


        // Get the schema definition
        final Catalog catalog;
        try {
            catalog = SchemaCrawlerUtility.getCatalog(getConnection(), options);
        } catch (SchemaCrawlerException e) {
            e.printStackTrace();
            return;
        }

        for (final Schema schema : catalog.getSchemas()) {
            if (!schema.getName().equals(configure.getSchema())) {
                log.info("omit schema {}", schema.getName());
                continue;
            }
            log.info("处理schema {}", schema.getName());

            Collection<Sequence> sequences = catalog.getSequences(schema);
            log.info("serial count {}", sequences.size());
            List<View> views = new ArrayList<View>();
            List<Table> tables = new ArrayList();
            List<Table> needToGeoshapeTransform = new ArrayList<>();
            for (final Table table : catalog.getTables(schema)) {


                String tableName = table.getName();

                if (inList(excludes, tableName)) {
                    continue;
                }
                if (includes.size() > 0) {
                    if (!inList(includes, tableName)) {
                        continue;
                    }
                }

                if (table instanceof View) {
                    views.add((View) table);
                } else {
                    tables.add(table);
                }


                if (configure.onlySchemaScript()) {
                    continue;
                }


                //   log.info("处理table ====================================={}.{}", schema.getName(), table.getName());
                boolean hasGeometryField = exportEntity(table, configure);
                if (hasGeometryField) {
                    needToGeoshapeTransform.add(table);
                    System.out.println("write to " + configure.daoPath() + table.getName() + "Entity2.java");
                    exportGeometryEntity(table, configure);
                }
                exportDao(table, configure);
                exportHTML(catalog.getTables(schema), configure);
            }
            String script = exportScript(sequences, tables, views, configure);

            String outputPath = configure.scriptPath() + "/" + configure.getSchema() + ".sql";
            org.nutz.lang.Files.write(outputPath, script);

        }
    }

    /**
     * 输出数据库重建脚本
     *
     * @param sequences
     * @param tables
     * @param views
     * @return
     */
    private String exportScript(Collection<Sequence> sequences, List<Table> tables, List<View> views, IConfigure configure) {
        StringBuilder sb = new StringBuilder(5000);
        String beforScript = readScript(configure.beforeScript());
        sb.append(beforScript);
        sb.append("\r\n");
        sb.append("--- 序列---\r\n");
        sb.append("--  total sequences " + sequences.size() + " -- \r\n");
        for (Sequence seq : sequences) {
            sb.append(
                    String.format("CREATE SEQUENCE IF NOT EXISTS \"%1$s\".\"%2$s\" INCREMENT %3$d MINVALUE  %4$d MAXVALUE %5$d START %6$s CACHE 1;\r\n",
                            seq.getSchema().getName(),
                            seq.getName(),
                            seq.getIncrement(),
                            seq.getMinimumValue(),
                            seq.getMaximumValue(),
                            seq.getStartValue() == null ? "1" : seq.getStartValue().toString()
                    ));
            sb.append(String.format("SELECT setval('\"%1$s\".\"%2$s\"', 1, false);\r\n",
                    seq.getSchema().getName(),
                    seq.getName()
            ));
            sb.append(String.format("ALTER SEQUENCE \"%1$s\".\"%2$s\" OWNER TO \"%3$s\";\r\n\r\n",
                    seq.getSchema().getName(),
                    seq.getName(),
                    configure.getUser()
            ));
        }


        for (Table table : tables) {
            sb.append(DbTools.toCreateScript(table, configure));
        }

        for (View view : views) {
            sb.append(DbTools.toCreateViewScript(view, configure));
        }

        String afterScript = readScript(configure.afterScript());
        sb.append(afterScript);
        return sb.toString();
    }

    private String readScript(String filePathName) {
        if (Strings.isBlank(filePathName)) {
            return "";
        }

        File file = new File(filePathName);
        if (!file.exists() || !file.isFile()) {
            log.warn("can not read file content " + filePathName);
            return "";
        }
        return org.nutz.lang.Files.read(file);
    }


    /**
     * 输出DB文档
     *
     * @param tables
     * @param configure
     */
    private void exportHTML(Collection<Table> tables, IConfigure configure) {


        StringBuilder document = new StringBuilder();
        document.append("<html><head>");
        document.append("<meta http-equiv=\"Content-Type\" content>");
        document.append("<style>");
        document.append(".sep{padding:10px 10px;margin:20px 0px;}");
        document.append("table {border-collapse:collapse; margin-top:15px;}");
        document.append(".head{font-weight:bold;");
        document.append("</style>");
        document.append("<body>");

        StringBuilder body = new StringBuilder();
        StringBuilder index = new StringBuilder();
        index.append("<table cellpadding='3' cellspacing='1'>");

        int tableIndex = 1;
        for (Table t : tables) {
            body.append("<a name='" + t.getName() + "'></a>");
            body.append("<table cellpadding='5' border='1' cellspacing='1'>");
            body.append("<tr >");
            body.append("<td >").append((tableIndex++)).append("</td>");
            body.append("<td colspan='6' class='head'>").append(t.getName()).append("</td>");
            body.append("<td>").append(t.getRemarks()).append("</td>");
            body.append("</tr>");
            body.append("<tr class='head'>");
            body.append("<td>").append("序号").append("</td>");
            body.append("<td>").append("名称").append("</td>");
            body.append("<td>").append("数据类型").append("</td>");
            body.append("<td>").append("长度").append("</td>");
            body.append("<td>").append("缺省值").append("</td>");
            body.append("<td>").append("主键").append("</td>");
            body.append("<td>").append("允许空值").append("</td>");
            body.append("<td>").append("说明").append("</td>");
            body.append("</tr>");

            int columnIndex = 1;
            for (Column c : t.getColumns()) {
                body.append("<tr>");
                body.append("<td>").append((columnIndex++) + "</td>");
                body.append("<td>").append(c.getName()).append("</td>");
                body.append("<td>").append(c.getColumnDataType().getDatabaseSpecificTypeName()).append("</td>");
                body.append("<td>").append(c.getSize() > 0 ? c.getSize() : "").append("</td>");

                body.append("<td>").append(c.getDefaultValue() != null ? c.getDefaultValue() : "").append("</td>");
                body.append("<td>").append(c.isPartOfPrimaryKey() ? "PK" : "").append("</td>");
                body.append("<td>").append(c.isNullable() ? "YES" : "").append("</td>");
                body.append("<td>").append(c.getRemarks()).append("</td>");
                body.append("</tr>");
            }

            body.append("</table>");
            index.append("<tr><td>" + (tableIndex - 1) + "</td><td><a href='#" + t.getName().trim() + "'>" + t.getName().trim() + "</a></td><td>" + t.getRemarks() + "</td></tr>");
        }
        index.append("</table>");

        document.append(index);
        document.append(body);
        document.append("</body>");

        org.nutz.lang.Files.write(configure.entityPath() + "/doc.html", document);
    }

    /**
     * 输出一个实体类
     *
     * @param table
     * @param configure
     */
    private void exportGeometryEntity(Table table, IConfigure configure) {
        log.info("export geometry entity " + table.getName());
        TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(getClassTypeName(table.getName()) + "Entity2")
                .addModifiers(Modifier.PUBLIC);
        typeBuilder.addJavadoc("<b>$L.$L</b>\r\n$L\r\n$L\r\n@author $L", table.getSchema().getCatalogName(), table.getName(), u(table.getRemarks()), table.getDefinition(), configure.author());

        if (configure.lombok()) {
            typeBuilder.addAnnotation(AnnotationSpec.builder(Data.class).build());
            if (table.getColumns().size() <= 15) {
                typeBuilder.addAnnotation(AnnotationSpec.builder(Builder.class).build());
                typeBuilder.addAnnotation(AnnotationSpec.builder(NoArgsConstructor.class).build());
                typeBuilder.addAnnotation(AnnotationSpec.builder(AllArgsConstructor.class).build());
            }
            typeBuilder.addAnnotation(AnnotationSpec.builder(Accessors.class).addMember("chain", "true").build());
        } else {
            //需要添加 get set方法
            for (Column column : table.getColumns()) {
                MethodSpec.Builder builder = MethodSpec.methodBuilder("get" + getClassTypeName(column.getName()));
                TypeName typeField = getDataType(column, column.getColumnDataType(), configure);
                if (typeField.toString().contains("org.postgresql.geometric")) {
                    typeField = ClassName.bestGuess(configure.geoObjectClass());
                }
                builder.returns(typeField);
                builder.addModifiers(Modifier.PUBLIC);
                builder.addStatement("return this.$L", camelConvert.convert(column.getName()));

                typeBuilder.addMethod(builder.build());
                builder = MethodSpec.methodBuilder("set" + getClassTypeName(column.getName()));
                builder.returns(TypeName.VOID);
                builder.addModifiers(Modifier.PUBLIC);
                builder.addParameter(typeField, camelConvert.convert(column.getName()));
                builder.addStatement("this.$L=$L", camelConvert.convert(column.getName()), camelConvert.convert(column.getName()));
                typeBuilder.addMethod(builder.build());
            }
        }
        for (Column column : table.getColumns()) {


            TypeName typeField = getDataType(column, column.getColumnDataType(), configure);
            if (typeField.toString().contains("org.postgresql.geometric")) {
                typeField = ClassName.bestGuess(configure.geoObjectClass());
            }
            FieldSpec.Builder fieldBuilder = FieldSpec.builder(typeField,
                    camelConvert.convert(column.getName()),
                    Modifier.PRIVATE);
            //添加字段注释
            fieldBuilder.addAnnotation(AnnotationSpec.builder(org.nutz.dao.entity.annotation.Comment.class).addMember("value", "$S", column.getRemarks()).build());

            typeBuilder.addField(fieldBuilder.build());
        }

        typeBuilder.addSuperinterface(Serializable.class);
        if (configure.getUseGwt()) {
            ClassName cn = ClassName.get("com.google.gwt.user.client.rpc", "IsSerializable");
            typeBuilder.addSuperinterface(cn);
        }
        JavaFile javaFile = JavaFile.builder(configure.entityPackage(), typeBuilder.build()).build();
        try {
            System.out.println("write to " + configure.daoPath() + table.getName() + "Entity2.java");
            javaFile.writeTo(new File(configure.entityPath()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 输出DAO
     *
     * @param table
     * @param configure
     */
    private void exportDao(Table table, IConfigure configure) {
        if (Strings.isBlank(configure.daoPackage())) {
            //只有配置DAO Package 才会输出DAO
            return;
        }
        String fileName = getClassTypeName(table.getName()) + "Dao";

        ClassName entityName = ClassName.get(configure.entityPackage(), getClassTypeName(table.getName()) + "Entity");

        ClassName parentClassName = ClassName.get("cn.mapway.dao", "BaseDao");
        ParameterizedTypeName t = ParameterizedTypeName.get(parentClassName, entityName);

        TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(fileName)
                .addModifiers(Modifier.PUBLIC)
                .superclass(t);


        typeBuilder.addAnnotation(AnnotationSpec.builder(Component.class).build());


        if (!configure.overrideDao()) {//不覆盖
            Boolean exist = isFileExist(configure.daoPath(), configure.daoPackage(), fileName + ".java");
            if (exist) {
                log.warn("存在Dao文件" + fileName);
                return;
            }
        }

        try {
            JavaFile javaFile = JavaFile.builder(configure.daoPackage(), typeBuilder.build())
                    .skipJavaLangImports(true)
                    .build();

            javaFile.writeTo(new File(configure.daoPath()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 文件存在
     *
     * @param daoPath    刀路
     * @param daoPackage dao包
     * @param fileName   文件名称
     * @return {@link Boolean}
     */
    private Boolean isFileExist(String daoPath, String daoPackage, String fileName) {
        Path outputDirectory = new File(daoPath).toPath();
        if (!daoPackage.isEmpty()) {
            String temp = daoPackage.replace(".", File.separator);
            outputDirectory = outputDirectory.resolve(temp);
            try {
                Files.createDirectories(outputDirectory);
            } catch (IOException e) {
            }
        }

        Path outputPath = outputDirectory.resolve(fileName);
        return outputPath.toFile().exists();

    }

    /**
     * 出口单位
     *
     * @param table     表格
     * @param configure 配置
     */
    private boolean exportEntity(Table table, IConfigure configure) {
        boolean hasGeometryFields = false;

        StringBuilder sqlSelect = new StringBuilder();
        sqlSelect.append("select ");
        for (Column column : table.getColumns()) {
            sqlSelect.append(column.getName()).append(",");
        }
        sqlSelect.deleteCharAt(sqlSelect.length() - 1).append(" from " + table.getName() + " ;");
        TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(getClassTypeName(table.getName()) + "Entity")
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(
                        AnnotationSpec.builder(org.nutz.dao.entity.annotation.Table.class).addMember("value", "value=$S", table.getName()).build());

        typeBuilder.addJavadoc("<b>$L.$L</b>\r\n$L\r\n$L\r\n@author $L", table.getSchema().getCatalogName(), table.getName(), u(table.getRemarks()), table.getDefinition(), configure.author());
        typeBuilder.addJavadoc("\r\n" + sqlSelect);
        if (configure.lombok()) {
            typeBuilder.addAnnotation(AnnotationSpec.builder(Data.class).build());
            if (table.getColumns().size() <= 15) {
                typeBuilder.addAnnotation(AnnotationSpec.builder(Builder.class).build());
                typeBuilder.addAnnotation(AnnotationSpec.builder(NoArgsConstructor.class).build());
                typeBuilder.addAnnotation(AnnotationSpec.builder(AllArgsConstructor.class).build());
            }
            typeBuilder.addAnnotation(AnnotationSpec.builder(Accessors.class).addMember("chain", "true").build());
        } else {
            //需要添加 get set方法
            for (Column column : table.getColumns()) {
                MethodSpec.Builder builder = MethodSpec.methodBuilder("get" + getClassTypeName(column.getName()));
                TypeName typeField = getDataType(column, column.getColumnDataType(), configure);
                if (typeField.toString().contains("org.postgresql.geometric")) {
                    hasGeometryFields = true;
                }
                builder.returns(typeField);
                builder.addModifiers(Modifier.PUBLIC);
                builder.addStatement("return this.$L", camelConvert.convert(column.getName()));

                typeBuilder.addMethod(builder.build());
                builder = MethodSpec.methodBuilder("set" + getClassTypeName(column.getName()));
                builder.returns(TypeName.VOID);
                builder.addModifiers(Modifier.PUBLIC);
                builder.addParameter(getDataType(column, column.getColumnDataType(), configure), camelConvert.convert(column.getName()));
                builder.addStatement("this.$L=$L", camelConvert.convert(column.getName()), camelConvert.convert(column.getName()));
                typeBuilder.addMethod(builder.build());
            }
        }

        //输出静态字段名称
        if (configure.withStaticField()) {
            for (Column column : table.getColumns()) {
                FieldSpec.Builder fieldBuilder = FieldSpec.builder(String.class, "FLD_" + column.getName().toUpperCase(), Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
                        .initializer("$S", column.getName());
                fieldBuilder.addJavadoc("$L\r\n数据库字段序号:$L", column.getName(), String.valueOf(column.getOrdinalPosition()));
                typeBuilder.addField(fieldBuilder.build());
            }
            FieldSpec.Builder fieldBuilder = FieldSpec.builder(String.class, "TBL_" + table.getName().toUpperCase(), Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
                    .initializer("$S", table.getName());
            fieldBuilder.addJavadoc("数据库表名称:$L", table.getName());
            typeBuilder.addField(fieldBuilder.build());

        }

        PrimaryKey primaryKey = table.getPrimaryKey();

        if (primaryKey != null && primaryKey.getConstrainedColumns().size() > 1) {
            //有联合主键
            AnnotationSpec.Builder unniColumns = AnnotationSpec.builder(PK.class);
            StringBuilder sb = new StringBuilder();
            sb.append("{");

            for (TableConstraintColumn c : primaryKey.getConstrainedColumns()) {
                if (sb.length() > 1) {
                    sb.append(",");
                }
                sb.append("\"").append(camelConvert.convert(c.getName())).append("\"");
            }
            sb.append("}");
            unniColumns.addMember("value", "value=$L", sb.toString());
            typeBuilder.addAnnotation(unniColumns.build());

            for (Column column : table.getColumns()) {
                TypeName type = getDataType(column, column.getColumnDataType(), configure);
                FieldSpec.Builder fieldBuilder = FieldSpec.builder(type,
                        camelConvert.convert(column.getName()),
                        Modifier.PRIVATE);

                if (type.toString().equals(Timestamp.class.getCanonicalName())
                        || type.toString().endsWith(Date.class.getCanonicalName())
                        || type.toString().endsWith(java.sql.Date.class.getCanonicalName())
                ) {
                    if (!Strings.isBlank(configure.dateFormat())) {
                        fieldBuilder.addAnnotation(AnnotationSpec.builder(JsonFormat.class)
                                .addMember("value", "pattern=$S", configure.dateFormat()).build());
                    }
                }

                fieldBuilder.addJavadoc("$L \r\n缺省值:$L\r\n数据类型 $L\r\n数据库字段长度:$L($L)",
                        u(column.getRemarks()), column.getDefaultValue(), column.getColumnDataType().getDatabaseSpecificTypeName(), column.getSize(),
                        column.isNullable() ? "允许为空" : "不允许为空");

                if (configure.withApiDoc()) {
                    String remark = Strings.isBlank(column.getRemarks()) ? column.getName() : column.getRemarks();
                    fieldBuilder.addAnnotation(AnnotationSpec.builder(ClassName.bestGuess("cn.mapway.document.annotation.ApiField")).addMember("value", "$S", remark).build());
                }
                if (column.isPartOfPrimaryKey()) {
                    //数据库字段和POJO字段不一致
                    if (!camelConvert.convert(column.getName()).equals(column.getName())) {
                        fieldBuilder.addAnnotation(AnnotationSpec.builder(org.nutz.dao.entity.annotation.Column.class).addMember("value", "$S", column.getName()).build());
                    }
                } else {
                    // 已经在类的PK注解上声明过了
                    fieldBuilder.addAnnotation(AnnotationSpec.builder(org.nutz.dao.entity.annotation.Column.class).addMember("value", "$S", column.getName()).build());
                }
                addColDefAnnotation(fieldBuilder, column, type);

                if (column.getDefaultValue() != null && column.getDefaultValue().length() > 0) {
                    AnnotationSpec.Builder defBuilder = AnnotationSpec.builder(Default.class);
                    defBuilder.addMember("value", "$S", column.getDefaultValue());
                    fieldBuilder.addAnnotation(defBuilder.build());
                }

                //添加字段注释
                fieldBuilder.addAnnotation(AnnotationSpec.builder(org.nutz.dao.entity.annotation.Comment.class).addMember("value", "$S", column.getRemarks()).build());
                typeBuilder.addField(fieldBuilder.build());
            }
        } else {
            //单主键
            for (Column column : table.getColumns()) {

                TypeName type = getDataType(column, column.getColumnDataType(), configure);
                FieldSpec.Builder fieldBuilder = FieldSpec.builder(type,
                        camelConvert.convert(column.getName()),
                        Modifier.PRIVATE);

                if (type.toString().equals(Timestamp.class.getCanonicalName())
                        || type.toString().endsWith(Date.class.getCanonicalName())
                        || type.toString().endsWith(java.sql.Date.class.getCanonicalName())
                ) {
                    if (!Strings.isBlank(configure.dateFormat())) {
                        fieldBuilder.addAnnotation(AnnotationSpec.builder(JsonFormat.class)
                                .addMember("value", "pattern=$S", configure.dateFormat()).build());
                    }
                }

                fieldBuilder.addJavadoc("$L \r\n缺省值:$L\r\n数据类型$L\r\n数据库字段长度:$L($L)",
                        u(column.getRemarks()), column.getDefaultValue(), column.getColumnDataType().getDatabaseSpecificTypeName(), column.getSize(),
                        column.isNullable() ? "允许为空" : "不允许为空");

                if (configure.withApiDoc()) {
                    String remark = Strings.isBlank(column.getRemarks()) ? column.getName() : column.getRemarks();
                    fieldBuilder.addAnnotation(AnnotationSpec.builder(ClassName.bestGuess("cn.mapway.document.annotation.ApiField")).addMember("value", "$S", remark).build());
                }
                if (column.isPartOfPrimaryKey()) {
                    if (isNumber(column)) {
                        //数值型主键
                        if (column.isAutoIncremented()) {
                            fieldBuilder.addAnnotation(AnnotationSpec.builder(Id.class)
                                    .build());
                        } else {
                            fieldBuilder.addAnnotation(AnnotationSpec.builder(Id.class)
                                    .addMember("auto", "$L", "false")
                                    .build());
                        }
                    } else {
                        //字符型主键
                        fieldBuilder.addAnnotation(AnnotationSpec.builder(Name.class).build());
                    }
                    if (!camelConvert.convert(column.getName()).equals(column.getName())) {// 字段名称和数据库列名称不一致，需要添加Column映射
                        fieldBuilder.addAnnotation(AnnotationSpec.builder(org.nutz.dao.entity.annotation.Column.class).addMember("value", "$S", column.getName()).build());
                    }
                } else {
                    //非主键 字段
                    fieldBuilder.addAnnotation(AnnotationSpec.builder(org.nutz.dao.entity.annotation.Column.class).addMember("value", "$S", column.getName()).build());
                }
                addColDefAnnotation(fieldBuilder, column, type);

                if (column.getDefaultValue() != null && column.getDefaultValue().length() > 0) {
                    AnnotationSpec.Builder defBuilder = AnnotationSpec.builder(Default.class);
                    defBuilder.addMember("value", "$S", column.getDefaultValue());
                    fieldBuilder.addAnnotation(defBuilder.build());
                }
                //添加字段注释
                fieldBuilder.addAnnotation(AnnotationSpec.builder(org.nutz.dao.entity.annotation.Comment.class).addMember("value", "$S", column.getRemarks()).build());
                typeBuilder.addField(fieldBuilder.build());
            }
        }


        typeBuilder.addSuperinterface(Serializable.class);
        if (configure.getUseGwt()) {
            ClassName cn = ClassName.get("com.google.gwt.user.client.rpc", "IsSerializable");
            typeBuilder.addSuperinterface(cn);
        }
        if (configure.withApiDoc()) {
            String remark = Strings.isBlank(table.getRemarks()) ? table.getName() : table.getRemarks();
            typeBuilder.addAnnotation(AnnotationSpec.builder(ClassName.bestGuess("cn.mapway.document.annotation.Doc")).addMember("value", "$S", remark).build());
        }
        JavaFile javaFile = JavaFile.builder(configure.entityPackage(), typeBuilder.build()).build();

        try {

            //  System.out.println("生成文件:" + configure.entityPath() + "/" + javaFile.packageName.replaceAll("\\.", "/") + getClassTypeName(table.getName()) + ".java");
            javaFile.writeTo(new File(configure.entityPath()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return hasGeometryFields;
    }

    private void addColDefAnnotation(FieldSpec.Builder fieldBuilder, Column column, TypeName type) {
        //添加ColDef 注解 用于根据Class创建数据库结构
        //   @ColDefine(type = ColType.PSQL_JSON)
        // int width() default 0;
        //    int precision() default 2;
        //    boolean notNull() default false;
        //    boolean unsigned() default false;
        //    boolean auto() default false;
        //    String customType() default "";
        //    boolean insert() default true;
        //    boolean update() default true;
        AnnotationSpec.Builder anBuilder = AnnotationSpec.builder(ColDefine.class);
        String typeName = column.getColumnDataType().getName().toUpperCase();

        //是否可以为空
        if (!column.getColumnDataType().isNullable()) {
            anBuilder.addMember("notNull", "$L", "true");
        }

        if (isJson(column)) {
            anBuilder.addMember("type", "$L", "org.nutz.dao.entity.annotation.ColType.PSQL_JSON");
        } else if (
                type.toString().endsWith("Long")
        ) {
            anBuilder.addMember("type", "$L", "org.nutz.dao.entity.annotation.ColType.INT");
            anBuilder.addMember("width", "$L", "19");
        } else if (
                type.toString().endsWith("Integer")

        ) {
            anBuilder.addMember("type", "$L", "org.nutz.dao.entity.annotation.ColType.INT");
            anBuilder.addMember("width", "$L", "10");
        } else if (
                type.toString().endsWith("Float")
                        || type.toString().endsWith("Double")
        ) {
            anBuilder.addMember("type", "$L", "org.nutz.dao.entity.annotation.ColType.FLOAT");
        } else if (
                type.toString().endsWith("Boolean")
        ) {
            anBuilder.addMember("type", "$L", "org.nutz.dao.entity.annotation.ColType.BOOLEAN");
        } else if (
                type.toString().endsWith("String")
        ) {
            if (typeName.equalsIgnoreCase("text")) {
                anBuilder.addMember("type", "$L", "org.nutz.dao.entity.annotation.ColType.TEXT");
            } else {
                anBuilder.addMember("type", "$L", "org.nutz.dao.entity.annotation.ColType.VARCHAR");
            }
        }
        fieldBuilder.addAnnotation(anBuilder.build());
    }

    private boolean isJson(Column column) {
        if (column == null) return false;
        String typeName = column.getColumnDataType().getName().toUpperCase();
        return "JSON".equals(typeName);
    }

    /**
     * 判断列是否是整数列
     *
     * @param column
     * @return
     */
    private boolean isNumber(Column column) {

        String[] numbers = Lang.array("INT", "BIGINT", "NUMBER", "SERIAL", "BIGSERIAL", "INT4", "INT8");
        return Lang.contains(numbers, column.getColumnDataType().getName().toUpperCase());
    }

    private boolean inList(List<String> includes, String table) {
        for (String s : includes) {
            if (s.compareToIgnoreCase(table) == 0) {
                return true;
            }
        }
        return false;
    }

    private String u(String s) {
        return s;
    }

    private String getClassTypeName(String name) {
        String temp = camelConvert.convert(name);
        return Strings.upperFirst(temp);
    }


    TypeName getDataType(Column c, ColumnDataType columnDataType, IConfigure configure) {
        // log.info("column " + c.getName() + " is " + c.getColumnDataType().getName());
        if (columnDataType.getName().equals("NUMBER") || columnDataType.getName().equalsIgnoreCase("NUMERIC")) {
            if (c.getDecimalDigits() <= 0) {
                if (c.getSize() <= 10) {
                    return TypeName.get(Integer.class);
                }
                return TypeName.get(Long.class);
            } else {
                return TypeName.get(Double.class);
            }
        }
        String typeName = columnDataType.getName().toUpperCase();
        if (
                typeName.startsWith("TIMESTAMP")
                        || typeName.startsWith("DATETIME")
                        || typeName.startsWith("DATE")
                        || typeName.startsWith("TIME")
        ) {
            return TypeName.get(Date.class);
        }
        if (typeName.equals("JSON")) {
            //如果是JSON数据类型 对应的ClassName从Columne的描述中获取
            String remarks = c.getRemarks();
            if (remarks == null || remarks.length() == 0) {
                return TypeName.get(String.class);
            } else {
                //是否是list
                String listExtractor = "list:(.*)\\s?";
                String listTypeFullName = StringTools.findInfo(listExtractor, remarks);
                if (!Strings.isBlank(listTypeFullName)) {
                    ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(ClassName.get(List.class), ClassName.bestGuess(listTypeFullName));
                    return parameterizedTypeName;
                }
                //不是list
                String typeExtractor = "type:(.*)\\s?";
                String fileTypeFullName = StringTools.findInfo(typeExtractor, remarks);
                if (Strings.isBlank(fileTypeFullName)) {
                    return TypeName.get(String.class);
                } else {
                    return ClassName.bestGuess(fileTypeFullName);
                }
            }

        }
        if (typeName.equals("UUID")) {
            return TypeName.get(String.class);
        }

        if (typeName.equals("SERIAL")) {
            return TypeName.get(Integer.class);
        }
        if (typeName.equals("BIGSERIAL")) {
            return TypeName.get(Long.class);
        }
        if (typeName.equals("GEOMETRY")) {
            return ClassName.bestGuess(configure.geoObjectClass());
        }
        if (typeName.equals("VARCHAR") || typeName.equals("_VARCHAR")
                || typeName.equals("TEXT") || typeName.equals("_TEXT")
                || typeName.equals("NAME") || typeName.equals("_NAME")
        ) {
            return TypeName.get(String.class);
        }
        if (typeName.equals("BOOL") || typeName.equals("_BOOL")) {
            return TypeName.get(Boolean.class);
        }
        if (typeName.equals("INT8") || typeName.equals("_INT8")) {
            return TypeName.get(Long.class);
        }
        if (typeName.equals("INT4") || typeName.equals("_INT4")) {
            return TypeName.get(Integer.class);
        }
        if (typeName.equals("FLOAT4") || typeName.equals("_FLOAT4")) {
            return TypeName.FLOAT.box();
        }
        if (typeName.equals("FLOAT8") || typeName.equals("_FLOAT8")) {
            return TypeName.DOUBLE.box();
        }
        Class temp = DataTypeMapper.resolve(typeName);
        if (temp != null) {
            return TypeName.get(temp);
        }
        return TypeName.get(columnDataType.getTypeMappedClass());
    }


}
